// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
#pragma once

// Processing agent that serializes simultaneous read and write operations to a shared resource.
// This is somewhat experimental, and feedback is welcome.

template <typename TReadResult, typename TWriteResult, typename ReadParameters = std::tuple<>, typename WriteParameters = std::tuple<>>
class SequentialReaderWriter : public concurrency::agent
{
public:
    SequentialReaderWriter()
    {
        m_processOperation = std::make_unique<concurrency::call<signal>>([this](signal s)
        {
            (void) s;
            assert(IsBackgroundThread());

            // TODO: Investigate how to make this write-preferred.

            // Select the next queue to receive a message. If both queues receive a message simultaneously,
            // the result is indeterminate.
            auto selector = concurrency::make_choice(&m_writeQueue, &m_readQueue);
            switch (concurrency::receive(selector))
            {
            case 0: // writer
                InternalWrite(selector.value<std::tuple<WriteParameters, concurrency::task_completion_event<TWriteResult>>>()).wait();
                break;

            case 1: // reader
                InternalRead(selector.value<std::tuple<ReadParameters, concurrency::task_completion_event<TReadResult>>>()).wait();
                break;
            }
        });
    }

    concurrency::task<TReadResult> ReadAsync(ReadParameters paramaters)
    {
        concurrency::task_completion_event<TReadResult> tce;
        concurrency::send(m_readQueue, std::make_tuple(paramaters, tce));
        concurrency::send(m_operationRequested, signal());
        return create_task(tce);
    }

    concurrency::task<TWriteResult> WriteAsync(WriteParameters paramaters)
    {
        concurrency::task_completion_event<TWriteResult> tce;
        concurrency::send(m_writeQueue, std::make_tuple(paramaters, tce));
        concurrency::send(m_operationRequested, signal());
        return create_task(tce);
    }

protected:
    void run()
    {
        m_operationRequested.link_target(m_processOperation.get());
    }

    virtual concurrency::task<void> InternalRead(const std::tuple<ReadParameters, concurrency::task_completion_event<TReadResult>>& t) = 0;
    virtual concurrency::task<void> InternalWrite(const std::tuple<WriteParameters, concurrency::task_completion_event<TWriteResult>>& t) = 0;

private:
    concurrency::unbounded_buffer<std::tuple<ReadParameters, concurrency::task_completion_event<TReadResult>>> m_readQueue;
    concurrency::unbounded_buffer<std::tuple<WriteParameters, concurrency::task_completion_event<TWriteResult>>> m_writeQueue;
    struct signal {};
    concurrency::unbounded_buffer<signal> m_operationRequested;
    std::unique_ptr<concurrency::call<signal>> m_processOperation;
};